/*
--1--
宏陷阱-宏定义不需要加;(分号)
写一个标准宏-MIN，这个宏输入两个参数并返回较小的一个。另外，当你写下面的代码时会发生什么事
#define MIN(A,B) ((A) <= (B)) ? (A) : (B)
如果传入-least = MIN(*p++, b);
则编译器替换为-((*p++) <= (b) ? (*p++) : (*p++))
可见指针P进行了三次++操作，已经无法实现预期功能



--2--
C/C++混合编程
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo，而C++编译器则会产生像_foo_int_int之类的名字
_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息，C++就是依靠这种机制来实现函数重载的。
为了实现C和C++的混合编程，C++提供了C连接交换指定符号extern"C"来解决名字匹配问题
函数声明前加上extern"C"后，则编译器就会按照C语言的方式将该函数编译为_foo
这样C语言中就可以调用C++的函数了




--3--
if()条件
if判断的是bool类型的真和假，对于int、指针类型写成if(!var)程序也能正确运行
规范写法是if(var==0)、if(var==NULL),var==0和var==NULL是逻辑判断，得到的结果
非真即假，传入真和假才是符合if的规范




--4--
数组名可以转换为指向其指代实体的指针，而且是一个指针常量，不能做自增、自减等操作，不能被修改
数组名作为函数形参时，在函数体内，数组名失去了本身的内涵，仅仅只是一个指针
在失去其内涵的同时，它还失去了其常量特性，可以作自增、自减等操作，可以被修改
char s[10];
s++;--编译出错，提示s不是左值
数组名作为函数形参时，沦为普通指针，此时在函数内可以进行自增、自减等操作




--5--
标准库函数
--strcpy--将strSrc复制给strDest
char* strcpy( char *strDest, const char *strSrc )--不考虑内存重叠
{
　assert( (strDest != NULL) && (strSrc != NULL) );--判断传入参数是否有效值
　char *address = strDest;--记录字符串源地址，便于返回
　while( (*strDest++ = *strSrc++) != '\0' );--复制操作实现
　return address;--返回复制好的字符串地址
}

--内存重叠版本
char* strcpy( char *strDest, const char *strSrc )
{

}

--strlen--strlen返回的值不包括'\0'
int strlen( const char *str )--输入参数const
{
　assert( strt != NULL );--断言字符串地址为有效值
　int len=0;--记录字符串长度
　while( (*str++) != '\0' )
　{
　　len++;
　}
　return len;
}

函数参数与变量生命周期--char *p--(*p)看成一个整体相当于 int p
void text1(char* p)--char* p 不是以指针传递的，而是以值传递的，在该函数内会复制一份P，在函数中
{                  --操作的是P的复制，并未直接操作传过来的指针，P仍为NULL
	p="shio";
}

char* text2()--char p[]在函数体内定义是局部变量，生命周期只在函数内，退出函数的时候，P就销毁了
{
　char p[] ="shio";
　return p;--可以值传递方式返回，不可返回指针，函数退出后，将成为一个未初始化的指针
}

void text3(char** p)--传入一个char* 类型的指针，在函数内直接操作传入的指针，不发生复制
{
	*p =(char*) malloc(10);--在此处申请内存，一定要记得使用完释放掉
    if(!p)--申请内存后应当检查是否申请成功
    {
        申请失败处理
    }
}

int main(int argc,char* argv[])
{
    char* stl=NULL;--创建指针一定要初始化置空，不初始化则是一个野指针，不知道指向哪里，会存在隐患
    text1(stl);--值传递传入stl的复制，stl仍为NULL
    if(stl)
    {
        printf("wsx");
    }
    stl=text2();--返回的是局部变量的指针，但函数返回后所指的内容已经被释放掉，继续对该指针操作会出错
    if(stl[0])--stl变成未初始化的指针，不知道所指的是什么内容
    {
        printf("wsx");
    }
    text1(&stl);--传入(*stl)的地址，并未以值传递，所以函数会改变stl的值
    printf(stl);
    free(stl);--堆区申请的内存使用完应当尽快释放掉
    stl=NULL;--释放后及时置空指针，防止其成为野指针
    return 0;
}


--6--
static变量
class a
{
public:
    static int as;--这里仅仅是静态变量的声明(不分配内存)，没有定义
};
int a::as=1;--定义(分配内存)--静态成员变量只能在类外定义并初始化(没有初始化就默认为0)
void main()
{
    cout<<a::as<<endl;--如果没有上面的那句定义，这里就会报错，只有定义了才会分配内存并默认初始化为0
}


--7--
NULL和nullptr、0 的区别
nullptr是C++11标准增加的关键字，相当于C语言中NULL，定义为(void*)0-空指针
C语言中NULL被宏定义为地址为0的空指针(void*)0
C++中NULL被宏定义为常量0，nullptr关键字定义为地址为0的空指针(void*)0
nullptr在任何时候都代表空指针(void)*，NULL宏定义为常量0，有些场景下可能不是(void)*,可能是其他的类型
C++中若用NULL作为指针的初始化，在遇到一些重载函数的时候会达不到预期甚至出错，如下
常量0可以转换成(void*)0、int、int*、double...--转换不可逆
void text(void* s1)
{
    printf("void*");
}
void text(int s1)
{
    printf("int");
}
void text(int* s1)
{
    printf("int*");
}
void mian()
{
    text(NULL);--出错--编译器预编译阶段将NULL替换成0，此时进入函数重载，0可以转换成void*、int、int*
               --编译器不知道选择哪一个，所以报错
    text(0);--输出打印-int-如果传参时直接写0，此时的0是有类型的，编译器默认为int型的0，另外两个需要
            --转换一次才能重载，第二个int型的不需要转换，所以直接调用第二个函数输出"int"
            --如果去掉第二个函数，再次编译就会出错，因为直接传入的0是编译器默认的int型，编译器知道调用
            --到第二个函数，去掉第二个int型的重载，剩下void*和int*，0都要经过一次转换才能重载函数
            --所以编译器，不知道该选择哪一个重载，于是报错
    text(nullptr);--输出打印-void*-C++中nullptr关键字被定义为(void*)0,为空指针，于是直接调用第一个
}
C++11之前可以自己定义一个nullptr类来表示空指针
const class nullptr_t
{
public:
    template<class T>
    inline operator T*() const{ return 0; }
    template<class C, class T>
    inline operator T C::*() const{ return 0; }
private:
void operator&() const;
} nullptr = {};



--8--
extern作用
A:用来声明或者引用一个全局变量，extern修饰的变量和函数可以被其他文件引用
extern int a;--声明
extern int a=0;、int a;、int a=0;--这些都是定义，会有分配内存初始化的动作
void text();--有分号，编译器知道这是函数声明，可以省去extern，变量要被外部文件引用就必须加上
在头文件中定义会被外部文件引用的全局变量，都需要加上extern声明
在cpp文件中定义会被外部文件引用的全局变量，不需要加上extern声明
如果在头文件定义会被外部文件引用的全局变量，若是多个文件包含了这个头文件，可能会出现该变量重复定义错误
所以尽量在头文件中声明，在源文件定义初始化，在需要引用的文件中，包含头文件或者直接用extern声明该引用

B:extern "c"
在c++环境中调用c的函数，常常会出现编译器无法找到该函数定义的错误
这是因为c++为了支持多态，比如重载和模板，c++编译时生成函数名非常复杂
而c语言不会，因此会造成找不到c函数的情况
此时C函数需要利用extern "c"进行链接指定，告诉编译器，按照C语言的标准编译



--9--
&&--左边如果为假，右边将不会被执行
如：a=b<c&&d=e;如果a=b<c不成立，那么d=e就不会被执行


--10--
volatile作用
CPU访问寄存器要比访问内存要快，因此编译器会进行编译优化告诉CPU把数据存在寄存器中，去访问寄存器的数据
不去内存中访问，但是在一些情况下比如多线程中，另一个线程在内存中改变了该数据，而本线程每次都只从寄存器
中读取该数据，所以是不知道该数据已经发生了改变，因此无法做出及时的调整，影响程序的运行
加上volatile声明，编译器就不会对该变量进行优化，会让CPU每次都去内存中读取该数据，以保证时效性
一个参数可以即是const又是volatile的，一个例子是只读状态寄存器
volatile是因为它可能被意想不到的被改变，const告诉程序不应该试图去修改他


--11--
析构函数能不能抛出异常
运行的程序抛出异常，则异常点之后的程序代码不会执行
如果析构函数抛出异常，万一在异常点之后存在一些必要的动作比如释放某些资源，抛出异常后则这些动作不会执行
会造成诸如资源泄漏的问题
通常异常发生时，c++的异常处理机制会调用已经该对象的析构函数来释放资源
此时若析构函数本身也抛出异常，则前一个异常尚未处理，又有新的异常，会造成程序崩溃的问题


--12--
复制拷贝构造函数不能传值
分析如下
class A{
private:
    int value;
public:
    A(int n){ value = n; }
    A(A other){ value = other.value; }--复制拷贝构造-值传递
    void Print() {cout<<value<<endl; }
};
int main(void)
{
    A a = 10;--传参构造-a创建成功
    A b = a;--传值复制拷贝构造--出错
            --传值需要复制一份，复制品也要进行拷贝构造，就会形成永无休止的递归调用从而导致栈溢出
    b.Print();
    return 0;
}
A b = a;--调用A(A other)，需要复制a给other，A other = a;但是other也没有初始化
        --于是other也要调用自己的拷贝构造,调用A(A others),others也要调用自己的拷贝构造
        --于是开始一直递归调用，直到栈溢出，A(A other(A(A others)))
        A(A                             1                       )
            A(A                         2                    )
                A(A                     3                 )
                    A(A                 4              )
                        A(A             5           )
                                       ...
--要复制拷贝构造b，需先将a复制一份实体，然后将复制的实体赋值给b以完成b的构造
这份实体也是类A的对象，既然是实体那就也需要调用构造函数初始化的，现在只有有两种构造方法
传int或者是传已经构造好的对象，我们传的是a，所以这份实体只能调用复制拷贝构造函数
当这份实体调用复制拷贝的时候，又需要复制另一份实体，来给当前实体赋值完成构造
但是新创建的实体也需要初始化，而且只能通过复制拷贝来创建
所以导致递归调用制拷贝构造函数，进入死循环


--13--
虚析构函数
析构函数是一个特殊的函数，编译器在编译时，析构函数的名字统一被编译为destucter
虽然父类子类的析构函数名字不同，但是经过编译后都叫做destucter，变成了同名
当父类的析构函数定义为虚函数时，由于子类的析构函数编译后与父类析构函数重名
于是构成重写，发生动态多态，调用子类的析构函数，根据继承机制，再自动调用父类析构函数



--14--
纯虚函数
没有函数体，含有纯虚函数的类称为抽象类，不能被实例化，纯虚函数只声明不定义
继承抽象类的子类必须实现父类所有的纯虚函数后，才能实例化
抽象类对象不能做函数参数,不能创建对象,不能作为函数返回类型
可以声明抽象类指针,可以声明抽象类的引用




--15--
二维数组--行储存
int a[3][4];--int** a--{int*,int*,int*}
a表示首地址，某些情况下，退化为指针
a[1][1]相当于*(*(a+1)+1)，a[1]--*(a+1)，[n]相当于重载--*(a+n)
a[1]+1==*(a+1)+1,还是一个int指针，不是int值，一级指针，指向第二行第二个
&a[1]+1==&(*(a+1))+1,&与*相抵消，等同于a+2,二级指针，指向第三行开头



--16--
指针传递
void text(int* p)
{
    *p=2;
}
指针传递的时候会创建一个临时指针，指向传过来的地址
int a=0;
text(&a);--text中有int* p=&a;p指向a，text中改变*p就是改变a的值，操作的是a的地址

void text2(char* s)
{
    s="shio";
}
char* l="text";--"text"在常量区，无法修改
text1(l);--text1中有char* s=*l;s指向的是"text"在常量区的地址
s="shio";把临时变量指针指向常量区的"shio"，!但是作为传值的char* l仍然指向"text"，没有发生修改
函数退出前后，char* l始终都指向"text"，不曾改变，我们传的是"text"的地址
函数中，我们创建一个临时指针s指向"text"，然后又让它指向"shio"，char* l不曾改动
主要考虑传指针的时候，我们会不会改变指针本身，如果我们只改变指针指向的内容，则可以直接传递
如果可能会改变该指针本身的指向，如char*常量或者链表的头节点，链表头节点为空或者要改变头节点的指向
就应该传入该指针的地址，而不是传入指针指向的地址


--17--
位运算--用于二进制中
与-&，逐位判断，两边都为1，结果为1，否则为0
10 0110 & 11 1011 = 10 0010
或-|，逐位判断，两边都不为0，结果为1，否则为0
10 0110 | 11 1011 = 11 1111
取反-~，逐位判断，所有位取相反，0变1，1变0
~10 0110 = 01 1001
左移-<<，所有位向左移N位，低位补0,超过8位舍弃
10 0110 << 2 = 1001 1000
右移->>，所有位向右移N位，无符号数高位补0,超过8位舍弃
10 0110 >> 2 = 0000 1001
异或-^，逐位判断，两边相同为0，不相同为1
10 0110 ^ 11 1011 = 01 1101

任何二进制数与0异或，都等于它本身,与本身异或则等于0
10 0110 | 00 0000 = 10 0110
10 0110 | 10 0110 = 00 0000
异或多用于交换两个变量，不需要中间值
int a=3,b=7;
a= a ^ b;--a = 3 ^ 7
b= a ^ b;--b = (3 ^ 7) ^ 7 = 3 ^ (7 ^ 7) = 3
a= a ^ b;--a = (3 ^ 7) ^ (3 ^ 7 ^ 7) = (3 ^ 3) ^ (7 ^ 7) ^ 7 = 7

在一个整数数组中，仅存在一个不重复的数字，其余数字均出现两次（或偶数次），找出不重复数字
int result = 0;
for (int index = 0; index < numArray.length; index++)
{
    result = result ^ numArray[index];
}
return result;
把数组所有的数都和0异或，只有一个不重复，其他的都是偶数次，偶数次异或等于0将消去重复数，与0异或不变
最后result(0)与不重复的数异或，结果result=不重复的数

n&(n-1)--计算n的二进制表示中有多少个1
int count=0;
while(n)
{
    n=n&(n-1);
    count++;--表示n二进制表示中1的个数
}
比如n=20，二进制n=10100，减去1，会逐渐向高位借位得到n-1=10011，
然后相与--10100 & 10011 = 10000，即去掉了n中的最后一个1，count++，统计个数
再n & n(n-1)--10000 & 01111 = 00000，最后一个1去掉了，count++,统计完成，n中二进制有两个1

用于判断一个数是否为2的幂次，若是，则它的二进制表示一定只有一个1
n & (n-1) = 0,说明n是2的整数次幂
int count=0;
while(n)
{
    n>>1;--每次右移一位，统计一次，直到n=0
    count++;--记录多少次幂
}

判断是否为奇数
n & 1
如 n=7,二进制 n=111，和1相与
n & 1 = 111 & 001 = 001 = 1，为奇数，只要是奇数，在二进制表示中，末位总为1
因此可以直接与1相与，判断是否为1，结果为1则为奇数



--18--
如何判断一段程序是由C 编译程序还是由C++编译程序编译的？
#ifdef __cplusplus--两个下划线__
        printf("This is cpp\n");
#else
        printf("This is C\n");
#endif
用C++写个程序，如何判断一个操作系统是16位还是32位的?
A:可以用sizeof,则直接sizeof(int),32是4，16是2
B:不能用sizeof
int a=~0;
if(a>65536)
    printf("This is 32\n");
else
    printf("This is 16\n");



--19--
Debug 通常称为调试版本，它包含调试信息，并且不作任何优化，便于程序员调试程序
Release 称为发布版本，它往往是进行了各种优化，使得程序在代码大小和运行速度上都是最优的



--20--
explicit关键字
只能用于修饰只有一个未定参数的类构造函数，可以有多个默认参数
作用是表明该构造函数是显式的, 而非隐式的, 防止类构造函数的隐式自动转换
如string中
string s1("text1");--构造函数显式标准传参
string s2="text2";--构造函数隐式标准传参，转换如下

string s2("text2");--转换成显式标准传参
string temp("text2");--或者创建一个临时变量
string s2=temp;--然后拷贝构造给s2
加上explicit关键字后，string s2="text2";将不被允许，只能string s1("text1");
跟它相对应的另一个关键字是implicit, 意思是隐藏的
类构造函数默认情况下即声明为implicit(隐式)


--21--
重载(overload)，重写-覆盖(override)，重定义(redefinition)
重载：同一作用域内，函数名相同，参数一定不同，返回值可以不同
重写：对于父类中声明为虚函数，在子类中函数名、参数、返回值和父类的一样，用于实现多态
重定义：父类中没有声明为虚函数，在子类中函数名和父类的一样，用于屏蔽父类的函数


--22--
函数模板和类模板的区别
函数模板的实例化是由编译器在处理函数调用时自动完成的，而类模板的实例化必须由程序员在程序中显式地指定
类模板用途
可用来创建动态增长和减小的数据结构
它是类型无关的，因此具有很高的可复用性
它在编译时而不是运行时检查数据类型，保证了类型安全
它是平台无关的，可移植性
可用于基本数据类型和自定义数据类型


--23--
TOP K
1. 排序
2. 分治
3. hash
4. 大小堆
5. 局部淘汰


--24--
malloc一次最大能申请多少内存
A:与CPU硬件和操作系统有关，一般分为32和64系统，32位系统下CPU寻址最大为 2^32 = 4GB
64位系统下 2^64 这个数字过大也不存在这种容量的内存，所以只使用了48位，高低位各24位，总256TB
B:程序有虚拟地址，分内核区和用户区，32位的Windows下内核与用户各2G空间，32位Linux下内核1G用户3G
内核区所有进程共享，用户区各自进程独享分为栈、映射区、堆、常量区、全局区、代码段
C:malloc在堆区开辟，所以32位系统下最大能开辟的内存应该小于2G（Windows）或者小于3G（Linux）
D:64位下Windows和Linux的内核区和用户区大小一样各128T，内核区在高位地址，用户区在低位地址
64位下malloc能申请的最大应该小于128T，因为其他分区如栈、映射区也会占据内存，不可能整个
用户区的内存都给堆区
F:一次申请的最大内存还取决于当前进程在堆区已经开辟了多少并且由于存在内存碎片也可能降低最大申请
能开辟多少和物理内存关系不大，因为采用的是虚拟地址，而且还有内存调度可以引发缺页中断使用交换分区


--25--
智能指针相关
智能指针对比裸指针额外的开销：引用计数的开销
智能指针引用计数的存储：可以使用int指针指向一块内存（多线程要加锁，堆区），构造和析构时将该
指针指向的值加减为0时，释放资源，智能指针对象在栈区，借助栈区离开作用域就会自动析构从而计数减一
智能指针内含的裸指针在堆区，需要计数为0时手动释放，计数器也存在堆区，计数器为0时，也释放掉


--26--
段页式管理功能：解决程序的虚拟地址到内存物理地址的映射
局部性：运行时不用一次全部装入，需要的时候触发中断调用相应的缺页进来
独立性：保证每个程序在运行的时候彼此独立互不干扰，采用虚拟地址




*/